package ph.com.gs3.formalistics.services;

import java.util.ArrayList;
import java.util.List;

import ph.com.gs3.formalistics.communicators.Communicator;
import ph.com.gs3.formalistics.communicators.CommunicatorModule.CommunicatorException;
import ph.com.gs3.formalistics.communicators.FormCommunicatorModule;
import ph.com.gs3.formalistics.managers.FormManager;
import ph.com.gs3.formalistics.model.dao.ConfigDAO;
import ph.com.gs3.formalistics.model.dao.FormsDAO;
import ph.com.gs3.formalistics.model.valueobjects.application.Form;
import ph.com.gs3.formalistics.utilities.FLLogger;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;

/**
 * 
 * @author Ervinne Sodusta
 * 
 */
public class FormsUpdateService extends Service {

	public static final String TAG = FormsUpdateService.class.getSimpleName();

	public static final String EXTRA_SERVER = "server";

	private FormManager formManager;
	private List<FormsUpdateListener> listeners;

	private String server;

	private final IBinder binder;

	private Looper serviceLooper;
	private UpdateServiceHandler updateServiceHandler;

	public FormsUpdateService() {
		listeners = new ArrayList<FormsUpdateListener>();
		binder = new FormsUpdateServiceBinder(this);

	}

	@Override
	public void onCreate() {
		// Start up the thread running the service. Note that we create a
		// separate thread because the service normally runs in the process's
		// main thread, which we don't want to block. We also make it
		// background priority so CPU-intensive work will not disrupt our UI.
		HandlerThread thread = new HandlerThread("ServiceStartArguments",
		        Process.THREAD_PRIORITY_BACKGROUND);
		thread.start();

		// Get the HandlerThread's Looper and use it for our Handler
		serviceLooper = thread.getLooper();
		updateServiceHandler = new UpdateServiceHandler(serviceLooper);

		FLLogger.d(TAG, "Service created");
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {

		// get the server as passed by the caller of this service
		server = intent.getExtras().getString(EXTRA_SERVER);

		// do not start the service if there is no server given
		if (server != null) {

			FLLogger.d(TAG, "FormsUpdateService started at server " + server + ". Start id: "
			        + startId);

			initializeFormManager();

			// For each start request, send a message to start a job and deliver the
			// start ID so we know which request we're stopping when we finish the job
			Message msg = updateServiceHandler.obtainMessage();
			msg.arg1 = startId;
			updateServiceHandler.sendMessage(msg);
		}

		FLLogger.d(TAG, "Service started");

		// We want this service to continue running until it is explicitly
		// stopped, so return sticky.
		return START_REDELIVER_INTENT;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}

	// ========================================================================
	// Functional Methods

	private void initializeFormManager() {

		ConfigDAO configDAO = new ConfigDAO(getApplicationContext());
		FormsDAO formsDAO = new FormsDAO(getApplicationContext());
		FormCommunicatorModule formCommModule = new FormCommunicatorModule(new Communicator(),
		        server);

		formManager = new FormManager(configDAO, formsDAO, formCommModule);

	}

	// ========================================================================
	// Service Handler

	private final class UpdateServiceHandler extends Handler {

		private boolean formsUpdatedSuccessfully = false;
		private List<Form> updatedForms = null;
		private Exception onUpdateException = null;

		private int starterId;

		public UpdateServiceHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {

			starterId = msg.arg1;

			FLLogger.d(TAG, "Service started updating forms");

			try {
				updatedForms = formManager.updateForms();
				formsUpdatedSuccessfully = true;
			} catch (CommunicatorException e) {
				formsUpdatedSuccessfully = false;
				onUpdateException = e;
			}

			FLLogger.d(TAG, "Update done");

			Handler postServiceExecuteHandler = new Handler(Looper.getMainLooper());
			postServiceExecuteHandler.post(postExecuteTask);
		}

		private Runnable postExecuteTask = new Runnable() {

			@Override
			public void run() {

				if (formsUpdatedSuccessfully) {
					notifyFinishedUpdatingForms(updatedForms);
				} else {
					notifyFailedUpdatingForms(onUpdateException);
				}

				// Stop the service using the startId, so that we don't stop
				// the service in the middle of handling another job
				stopSelf(starterId);

			}

		};

	}

	// ========================================================================
	// Interfaces and Notification Utility Methods

	public static interface FormsUpdateListener {
		public void onFinishedUpdatingForms(List<Form> updatedForms);

		public void onFailedUpdatingForms(Exception e);
	}

	private synchronized void notifyFinishedUpdatingForms(List<Form> updatedForms) {
		for (FormsUpdateListener listener : listeners) {
			listener.onFinishedUpdatingForms(updatedForms);
		}
	}

	private synchronized void notifyFailedUpdatingForms(Exception e) {
		for (FormsUpdateListener listener : listeners) {
			listener.onFailedUpdatingForms(e);
		}
	}

	// ========================================================================
	// Binder

	public static class FormsUpdateServiceBinder extends Binder {

		private FormsUpdateService service;

		public FormsUpdateServiceBinder(FormsUpdateService service) {
			this.service = service;
		}

		public synchronized void registerListener(FormsUpdateListener listener) {
			service.listeners.add(listener);
		}

		public synchronized void removeListener(FormsUpdateListener listener) {
			service.listeners.remove(listener);
		}

	}

}
